/*
 * SRMI (an easy-to-use zero-code remoting library for Java)
 * Copyright (C) 2007  Mohammad Nabil Hussein
 * mohammad (dot) nabil (dot) h (at) gmail (dot) com
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */

package SRMI.RemotingServices;

import SRMI.RemotingServices.ClassGeneration.Client.RmiStubAdapterGenerator;
import SRMI.SRMIException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.UUID;
import org.apache.log4j.Logger;

/**
 * This class manages the client side. Looking up and retriving remote object
 * is done using this class.
 * @author meemo
 */
public class ClientUtils
{
    private final static int remoteRegistryPortNumber = CommonUtils.registryPortNumber;
    
    /**
     * Indicates that if many instances exists of the requested class, at the remote
     * host, return any of them.
     */
    public static final UUID ANY_INSTANCE = UUID.nameUUIDFromBytes("ANY_INSTANCE".getBytes());
    
    private static final Logger l = Logger.getLogger(ClientUtils.class);
    
    /**
     * Get all instances of requestedClass on the host specified by
     * serverAddr.
     * @param requestedClass The class to search for. That doesn't support polymorphism
     * @param serverAddr The Address of the remote server. Can be "localhost".
     * @return List of all instances of the specified class, identified by their UUID.
     * @throws SRMI.SRMIException
     */
    public static HashMap<UUID,Object> getRemoteObject(Class requestedClass,String serverAddr) throws SRMIException
    {
        if(java.lang.System.getProperty("srmi.basedir") == null)
        {
            l.info("srmi.basedir is not set! quitting");
            return null;
        }
        
        if(serverAddr.contains("localhost")
        || serverAddr.contains("127.0.0.1")
        || serverAddr.contains("127.0.1.1"))
        {
            serverAddr = CommonUtils.detectExternalInterfaceIP();
        }
        
        Registry registry = null;
        try
        {
            registry = LocateRegistry.getRegistry(serverAddr, remoteRegistryPortNumber);
        }
        catch(RemoteException ex)
        {
            throw new SRMIException("Can't get the remote registry (RemoteException) " + serverAddr + ":" + remoteRegistryPortNumber,ex);
        }
        if(registry == null)
        {
            l.error("Can't get remote registry on machine " + serverAddr);
            throw new SRMIException("Can't get the remote registry (null) " + serverAddr + ":" + remoteRegistryPortNumber);
        }
        
        int i = 0;
        HashMap<UUID,Object> availableObjects = new HashMap<UUID,Object>();
        Class<?> clientside_adapter = new RmiStubAdapterGenerator(requestedClass).GetGenerated(); // must be generated before lookup so the lookup routine finds the generated interface
        
        String existingServices[] = null;
        try
        {
            existingServices = registry.list();
        }
        catch(Exception ex)
        {
            throw new SRMIException("Can't get the list of services in the RMI Registry " + serverAddr + ":" + remoteRegistryPortNumber, ex);
        }
        
        for(String service : existingServices)
        {
            l.info(String.format("\t+ (%d) %s",i++,service));
            if(service.substring(0,service.indexOf(':')).equals(requestedClass.getCanonicalName()))
            {
                UUID uuid = UUID.fromString(service.substring(service.indexOf(':')+1));
                try
                {
                    availableObjects.put
                        (uuid,
                        clientside_adapter.
                        getConstructor(Object.class,UUID.class,String.class).
                        newInstance(registry.lookup(service),uuid,serverAddr));
                }
                catch(Exception ex)
                {
                    throw new SRMIException("Cant' instantiate the generated stub adapter!",ex);
                }
            }                
        }
        
        return availableObjects;        
    }
    
    /**
     * Get a single object of the requestedClass, that exists on the host identified by
     * serverAddr. If many exists, get the one with the specified ID. ID can be ClientUtils.ANY_INSTANCE
     * @param requestedClass The class of which the requested object is instantiated. This doens't support polymorphism.
     * @param serverAddr The address of the remote host. Can be "localhost"
     * @param ID The UUID identifying the object wanted.
     * @return The object found. If no object exists with the specified UUID, or with the specified class
     * null is returned.
     * @throws SRMI.SRMIException
     */
    public static Object getRemoteObject(Class requestedClass,String serverAddr,UUID ID) throws SRMIException
    {
        String msg = null;
        Object obj = null;
        Map<UUID,Object> objects = getRemoteObject(requestedClass,serverAddr);
        if(!ID.equals(ANY_INSTANCE))
        {
            msg = String.format("Trying to get //%s:%d/%s:%s ... ",
                    serverAddr,remoteRegistryPortNumber,requestedClass.getCanonicalName(),ID.toString());
            
            l.info(msg + objects.size() + " objects of type " + requestedClass.getCanonicalName() + " found :");
            
            for(Map.Entry<UUID,Object> entry : objects.entrySet())
                l.info(entry.getKey().toString());
            obj = objects.get(ID);
        }
        else
        {
            msg = String.format("Trying to get //%s:%d/%s:ANY_INSTANCE ... ",
                    serverAddr,remoteRegistryPortNumber,requestedClass.getCanonicalName());
            
            Iterator<Map.Entry<UUID,Object> > itr =
                    objects.entrySet().iterator();
            if(itr.hasNext())
                obj = itr.next().getValue();
        }
        l.info(msg + (obj == null ? "none" : "found"));
        return obj;
    }
}
